#!/usr/bin/python
'''
@author: varounisdi
'''
import subprocess
import glob
import os
import shutil
import platform
import re
import operator
import sys
import tarfile
import traceback
########################################################################################################
#class WildThread
#Describes a thread
########################################################################################################
class WildThread:
    def __init__(self, tid, rate, details=None):
        self.tid = tid
        self.rate = rate
        self.details = details        
        

    def printWildThread(self):
        """Print all thread information"""
        print 'TID:{0}'.format(self.tid)
        print 'Thread Rate(Average CPU Usage):{0}%'.format(self.rate)
        if self.details != None:
            print 'Thread Stack Trace:{0}'.format(self.details)
#jcorefiles = glob.glob('/opt/ibm/websphere/appserver/profiles/AppSrv01/javacore.%s%s%s.*' % (datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day))
#######################################################################################################
#class ThreadAnalysis
#Performs the Thread Analysis
########################################################################################################
class ThreadAnalysis:
    TopN = 5
    WildThreadList = []
    JAVAPLATFORM = []
    
    @classmethod
    def RateJavaThreadsPerformance(cls, topHtxt):
        '''Read the topdashH file and find the topN threads with highest CPU consumption'''
        reiter = re.finditer('^(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\d+)\s+.+$', topHtxt, re.MULTILINE)
        D = {}
        
        #Sum the CPU usage for each thread found in topdashH file
        for m in reiter:
            if m.group(1) not in D:
                D[m.group(1)] = int(m.group(2))
            else:
                D[m.group(1)] = D[m.group(1)] + int(m.group(2))
        #Sort the thread list by total cpu 
        sorted_ThList = sorted(D.iteritems(), key=operator.itemgetter(1), reverse=True)
        #Get the top N threads
        topFiveTh = sorted_ThList[:cls.TopN]
        
        #Divide the total CPU usage of top N threads by the occurence of them in topdashH file 
        for t in topFiveTh:
            countMatches = len(re.findall('^%s.+$' % t[0], topHtxt, re.MULTILINE))
            s = str(hex(int(t[0])))[2:]
            cls.WildThreadList.append(WildThread(s.upper(), t[1] / countMatches))

    @classmethod
    def __RunsJava5(cls):
        if cls.JAVAPLATFORM[1].startswith('5.'):
            return True
    
    @classmethod
    def __RunsJava6(cls):
        if cls.JAVAPLATFORM[1].startswith('1.6'):
            return True

    
    @classmethod
    def FindThreadsDetails(cls):
        """Analyze the cores and find details(stack trace etc) about the top 5 CPU Consuming threads"""
        jclistl = glob.glob('./javacore.*')
        for i in cls.WildThreadList:
            for jc in jclistl:
                #Select only the threads region of javacore file(JRE 5 or 1.6)
                thdumptxt = ""
                if cls.__RunsJava5():
                    for line in open(jc, 'r'):
                        if line.startswith('3XMTHREADINFO') or line.startswith('4XESTACKTRACE'):
                            thdumptxt += line
                elif cls.__RunsJava6():
                    inThreadArea = False
                    for line in open(jc, 'r'):
                        if line.startswith('1XMTHDINFO'):
                            inThreadArea = True
                        elif line.startswith('1SCLTEXTCRTW'):
                            inThreadArea = False
                            break
                        elif inThreadArea == True:
                            thdumptxt += line
                else:
                    raise UnknownJREException('Current JRE is not a known JRE...')
                    
                #Get the specific thread details (stack trace etc)
                if cls.__RunsJava5():
                    match = re.search('^.*3XMTHREADINFO(.+?native\sID\:0x[0]+%s.+?)3XMTHREADINFO.*$' % i.tid, thdumptxt, re.DOTALL)
                elif cls.__RunsJava6():
                    match = re.search('^.*NULL(.+?native\sthread\sID\:0x%s.+?)NULL.*$' % i.tid, thdumptxt, re.DOTALL)
                    
                if match != None:
                    i.details = match.group(1)
                    break
    
    @classmethod
    def FindJavaVersion(cls):
        """Find the JRE version and search for the appropriate patterns"""
        jclistl = glob.glob('./javacore.*')
        for line in open(jclistl[0], 'r'):
            if line.startswith('1CIJAVAVERSION'):
                match = re.search('^1CIJAVAVERSION\s+?(\w+)\s+?(\S+)\s+.+$', line)
                if match != None:
                    cls.JAVAPLATFORM.append(match.group(1))
                    cls.JAVAPLATFORM.append(match.group(2))

    @classmethod      
    def printThreadList(cls):
        """"Print the Contents of Thread list"""
        for i in cls.WildThreadList:
            i.printWildThread()


########################################################################################################

########################################################################################################
class DataCollector:
 
    WASPROFHOME = '/opt/ibm/websphere/appserver/profiles/AppSrv01'
    WASJAVA = '/opt/ibm/websphere/appserver/java/bin/java'
    WASUSER = 'was1'
    TOPDELAY = 5
    TOPREPETS = 5

    @classmethod
    def CreateThreadDump(cls):
        if platform.system() == 'Linux':
                while not os.path.exists(cls.WASJAVA):
                        print 'Could not find the WAS Java executable...'
                        cls.WASJAVA = raw_input('Please provide the WAS Java execurable:')
        gentdumpcmd = '/usr/bin/sudo /bin/fuser -k -3 %s' % cls.WASJAVA
        fuproc=subprocess.Popen(gentdumpcmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True)
        print 'Generating Thread Dump for process '+fuproc.communicate()[0]

    
    @classmethod
    def CallTopH(cls):
        pgrepcmd = "/usr/bin/pgrep -u %s java" % cls.WASUSER
        pgrepout = subprocess.Popen(pgrepcmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True)
        pid = pgrepout.communicate()[0]
        topHcmd = '/usr/bin/top -bH -d %s -n %s -p %s' %(cls.TOPDELAY, cls.TOPREPETS, pid)
        tHproc = subprocess.Popen(topHcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        topHtxt = tHproc.communicate()[0]
        return topHtxt


    @classmethod
    def ArchiveOldJavacores(cls):
        jclist = glob.glob('./javacore.*.txt')
        if not jclist:
            print 'No javacores to archive'
        else:
            print 'Archiving older javacores...'
            tar=tarfile.open("javacores.tar","w")
            for jc in jclist:
                tar.add(jc)
                os.remove(jc)
            tar.close()


    @classmethod
    def MoveJavacoresHere(cls):
            """Move Javacores to current dir(i.e. where linperf lies)"""
            if platform.system() == 'Linux':
                while not os.path.exists(cls.WASPROFHOME): 
                        print 'Could not find the Javacores...'
                        cls.WASPROFHOME = raw_input('Please provide the WAS Profile under which linperf ran:')
                        
                jclist = glob.glob(cls.WASPROFHOME + '/javacore.*')
                print 'Moving Javacores in current directory...'
                for jc in jclist:
                    shutil.move(jc, '.')




########################################################################################################
#class UnknownJREException
#Exception class that is raised when a not known JRE is found
########################################################################################################
class UnknownJREException(Exception):
    pass
########################################################################################################
if __name__ == '__main__':
    try:
        DataCollector.ArchiveOldJavacores()
        DataCollector.CreateThreadDump()
        topHoutput = DataCollector.CallTopH()
        DataCollector.CreateThreadDump()
        DataCollector.MoveJavacoresHere()
        ThreadAnalysis.FindJavaVersion()
        ThreadAnalysis.RateJavaThreadsPerformance(topHoutput)
        ThreadAnalysis.FindThreadsDetails()
        ThreadAnalysis.printThreadList()
    except Exception as X:
        print X
        traceback.print_exc()
        print 'Exiting...'
        sys.exit(1)

